package auth

import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.jwk.JWKSet
import com.nimbusds.jose.jwk.KeyUse
import com.nimbusds.jose.jwk.RSAKey
import grails.compiler.GrailsCompileStatic
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.ClassPathResource
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer
import org.springframework.security.oauth2.provider.ClientDetailsService
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService
import org.springframework.security.oauth2.provider.token.TokenStore
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory

import javax.annotation.Resource
import javax.sql.DataSource
import javax.transaction.Transactional
import java.security.KeyPair
import java.security.interfaces.RSAPublicKey

@Configuration
@GrailsCompileStatic
@EnableAuthorizationServer
class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    private static final String KEY_STORE_FILE = "keystore.jks"
    private static final String KEY_STORE_PASSWORD = "password"
    private static final String KEY_ALIAS = "marsdonne"
    private static final String JWK_KID = "my-kid"

    @Resource
    private AuthenticationManager authenticationManager

    @Resource
    private DataSource dataSource

    @Bean
    ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource)
    }

    @Bean
    UserDetailsService userDetailsService() {
        return new UserDetailsService() {
            @Override
            @Transactional
            UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                def user4Client = User4Client.findByCode(username)

                if (user4Client)
                    new UserEx(user4Client.code, user4Client.deviceId, "real name", [])
                else
                    throw new UsernameNotFoundException("Not found user")
            }
        }
    }

    @Bean
    KeyPair keyPair() {
        def keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(KEY_STORE_FILE), KEY_STORE_PASSWORD.toCharArray())
        keyStoreKeyFactory.getKeyPair(KEY_ALIAS)
    }

    @Bean
    JWKSet jwkSet() {
        new JWKSet(
                new RSAKey.Builder((RSAPublicKey) keyPair().getPublic())
                        .algorithm(JWSAlgorithm.RS256)
                        .keyUse(KeyUse.SIGNATURE)
                        .keyID(JWK_KID)
                        .build())
    }

    @Bean
    JwtAccessTokenConverter accessTokenConverter() {
        return new JwsAccessTokenConverter([kid: JWK_KID], keyPair())
    }

    @Bean
    TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        new JwtTokenStore(jwtAccessTokenConverter)
    }

    @Override
    void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService())
    }

    @Override
    void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .accessTokenConverter(accessTokenConverter())
                .reuseRefreshTokens(false)
                .userDetailsService(userDetailsService())
    }

    @Override
    void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
//                .allowFormAuthenticationForClients()
    }
}
